use std::path::Path;

use bc_base::{BlockHeight, BlockId, TimeStamp, block::Block, misc_item::MiscItemCache};
use bc_block_data::MiscItemStorage;

use crate::top_storage::TopStorageError;

#[derive(Debug)]
pub struct MiscItemStorageWithCache {
    misc_item_cache: MiscItemCache,
    misc_item_storage: MiscItemStorage,
}

impl MiscItemStorageWithCache {
    pub async fn new(db_root_dir: &Path) -> Self {
        let misc_item_storage = MiscItemStorage::new(db_root_dir).unwrap();
        let mut t = Self {
            misc_item_cache: MiscItemCache::new(),
            misc_item_storage,
        };

        bc_util::execute_function_elapsed("load_to_misc_item_cache", async {
            t.load_misc_item_to_cache().await;
        })
        .await;

        t
    }
}

// for write
impl MiscItemStorageWithCache {
    pub async fn write_latest_blk_id_in_best_chain(
        &mut self,
        block_id: &BlockId,
    ) -> Result<(), TopStorageError> {
        self.misc_item_storage
            .put_latest_block_id_in_best_chain(block_id)
            .await?;
        self.misc_item_cache
            .write_latest_block_id_in_best_chain(block_id);
        Ok(())
    }

    pub async fn write_best_height(
        &mut self,
        best_height: BlockHeight,
    ) -> Result<(), TopStorageError> {
        self.misc_item_storage.put_best_height(&best_height).await?;
        self.misc_item_cache.write_best_height(best_height);
        Ok(())
    }

    pub async fn write_time_best_received(&mut self) -> Result<(), TopStorageError> {
        let time_best_received = bc_util::get_current_sys_time_seconds();
        self.misc_item_storage
            .put_time_best_received(&time_best_received)
            .await?;
        self.misc_item_cache
            .write_time_best_received(time_best_received);
        Ok(())
    }

    pub async fn flush_database(&self) -> Result<(), TopStorageError> {
        self.misc_item_storage.flush_database().await?;
        Ok(())
    }

    pub async fn update_misc_storage_for_genesis_block(
        &mut self,
        block_id: &BlockId,
        block: &Block,
    ) -> Result<(), TopStorageError> {
        self.misc_item_storage
            .update_misc_storage_for_genesis_block(block_id, block)
            .await?;
        self.load_misc_item_to_cache().await;
        Ok(())
    }
}

// for read
impl MiscItemStorageWithCache {
    pub fn read_latest_block_id_in_best_chain(&self) -> BlockId {
        *self.misc_item_cache.read_latest_block_id_in_best_chain()
    }

    pub fn read_best_height(&self) -> BlockHeight {
        self.misc_item_cache.read_best_height()
    }

    pub fn read_time_best_received(&self) -> TimeStamp {
        self.misc_item_cache.read_time_best_received()
    }

    async fn load_misc_item_to_cache(&mut self) {
        let latest_block_id_in_best_chain = self
            .misc_item_storage
            .get_latest_block_id_in_best_chain()
            .await
            .unwrap();
        if let Some(t) = latest_block_id_in_best_chain {
            self.misc_item_cache.write_latest_block_id_in_best_chain(&t);
        }

        let best_height = self.misc_item_storage.get_best_height().await.unwrap();
        if let Some(t) = best_height {
            self.misc_item_cache.write_best_height(t);
        }

        let time_best_received = self
            .misc_item_storage
            .get_time_best_received()
            .await
            .unwrap();
        if let Some(t) = time_best_received {
            self.misc_item_cache.write_time_best_received(t);
        }
    }
}
